﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;

namespace Djs.Common.Components
{
    /// <summary>
    /// Library of images
    /// </summary>
    public class IconLib
    {
        #region Singleton
        protected static IconLib I
        {
            get
            {
                if (_I == null)
                {
                    lock (_ILock)
                    {
                        if (_I == null)
                        {
                            _I = new IconLib();
                        }
                    }
                }
                return _I;
            }
        }
        private static IconLib _I;
        private static object _ILock = new object();
        #endregion
        #region Public acces to image
        /// <summary>
        /// Return an image loaded from file from directory "bin/../[../../../]pic/*(size)/name.{png|jpg|gif|bmp}.
        /// Can return image with bigger or smaller size.
        /// </summary>
        /// <param name="name"></param>
        /// <param name="size"></param>
        /// <returns></returns>
        public static Image Image(string name, IconSize size)
        {
            return I._GetImage(name, (int)size, IconState.Standard);
        }
        /// <summary>
        /// Return an image loaded from file from directory "bin/../[../../../]pic/*(size)/name.{png|jpg|gif|bmp}.
        /// Can return image with bigger or smaller size.
        /// </summary>
        /// <param name="name"></param>
        /// <param name="size"></param>
        /// <returns></returns>
        public static Image Image(string name, int size)
        {
            return I._GetImage(name, size, IconState.Standard);
        }
        /// <summary>
        /// Return an image loaded from file from directory "bin/../[../../../]pic/*(size)/name(status).{png|jpg|gif|bmp}.
        /// Can return image with bigger or smaller size.
        /// Can return image with supplemental status.
        /// </summary>
        /// <param name="name"></param>
        /// <param name="size"></param>
        /// <param name="state"></param>
        /// <returns></returns>
        public static Image Image(string name, IconSize size, IconState state)
        {
            return I._GetImage(name, (int)size, state);
        }
        /// <summary>
        /// Return an image loaded from file from directory "bin/../[../../../]pic/*(size)/name(status).{png|jpg|gif|bmp}.
        /// Can return image with bigger or smaller size.
        /// Can return image with supplemental status.
        /// </summary>
        /// <param name="name"></param>
        /// <param name="size"></param>
        /// <param name="state"></param>
        /// <returns></returns>
        public static Image Image(string name, int size, IconState state)
        {
            return I._GetImage(name, size, state);
        }
        #endregion
        #region Private icon library mechanism: constructor, file list loading, icon search
        private IconLib()
        {
            string path = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location); // app/bin [?Debug]
            for (int t = 0; t < 6; t++)
            {
                string test = System.IO.Path.Combine(path, "pic");
                if (System.IO.Directory.Exists(test))
                {
                    path = test;
                    break;
                }
                path = System.IO.Path.GetDirectoryName(path);
                if (path.Length < 5) break;
            }

            this._ImageDict = new Dictionary<string, IconImages>();
            this._ImagePath = path;
            this._FileLists = new List<FileList>();
            this._LoadFileList();
            this._FillConstants();
        }
        /// <summary>
        /// Fill list of directories (bin\pic\*) to this._FileLists
        /// </summary>
        private void _LoadFileList()
        {
            System.IO.DirectoryInfo di = new System.IO.DirectoryInfo(this._ImagePath);
            if (!di.Exists) return;
            System.IO.DirectoryInfo[] subDirs = di.GetDirectories();
            foreach (System.IO.DirectoryInfo subDir in subDirs)
            {
                FileList fileList = FileList.CreateForDir(subDir);
                if (fileList != null)
                    this._FileLists.Add(fileList);
            }
            this._FileLists.Sort((a, b) => a.Size.CompareTo(b.Size));
        }
        private void _FillConstants()
        {
            this._StateSuffix = new Dictionary<IconState, string[]>();
            this._StateSuffix.Add(IconState.Standard, ";_std;_a".Split(';'));
            this._StateSuffix.Add(IconState.Disable, "_dis;_d".Split(';'));
            this._StateSuffix.Add(IconState.Hot, "_hot;_h".Split(';'));
            this._StateSuffix.Add(IconState.Focused, "_foc;_f".Split(';'));
            this._StateSuffix.Add(IconState.Pressed, "_dwn;_p".Split(';'));

            this._StateSupplements = new Dictionary<IconState, IconState[]>();
            this._StateSupplements.Add(IconState.Standard, new IconState[] { IconState.Standard });
            this._StateSupplements.Add(IconState.Disable, new IconState[] { IconState.Disable, IconState.Standard });
            this._StateSupplements.Add(IconState.Hot, new IconState[] { IconState.Hot, IconState.Pressed, IconState.Focused, IconState.Standard });
            this._StateSupplements.Add(IconState.Focused, new IconState[] { IconState.Focused, IconState.Hot, IconState.Pressed, IconState.Standard });
            this._StateSupplements.Add(IconState.Pressed, new IconState[] { IconState.Pressed, IconState.Hot, IconState.Focused, IconState.Standard });

            this._FileExtensions = ".png;.gif;.jpg;.bmp".Split(';');
        }
        /// <summary>
        /// Returns Image for specified name, size and state
        /// </summary>
        /// <param name="name"></param>
        /// <param name="size"></param>
        /// <param name="state"></param>
        /// <returns></returns>
        private Image _GetImage(string name, int size, IconState state)
        {
            if (String.IsNullOrEmpty(name))
                throw new ArgumentNullException("name", "IconLib[name]: name can not be empty or null value.");
            string key = name.ToLower().Trim();
            IconImages icon;
            if (!this._ImageDict.TryGetValue(key, out icon))
            {
                icon = new IconImages(key, name, this);
                this._ImageDict.Add(key, icon);
            }

            return icon.Image(size, state);
        }
        /// <summary>
        /// Return a list of files for specified size (or greater, or smaller).
        /// If "shift" is not zero, then return images smaller (for negative shift) or bigger (positive shift).
        /// Value of "shift" is not in pixel, but in count of "set".
        /// For example, when exists size: 8,16,24,32,48,64, and requested size = 32, and shift= -1, then return is with size = 24.
        /// For requested size = 28 and shift = 0 is return set size = 32 (nearest bigger), for shift= -1, then return is with size = 24.
        /// Returns null only when does not exists any path (this.FileLists.Count == 0).
        /// </summary>
        /// <param name="size"></param>
        /// <param name="shift"></param>
        /// <returns></returns>
        protected FileList GetFileListForSize(int size, int shift)
        {
            if (this.FileLists.Count == 0) return null;

            int idx = this.FileLists.FindIndex(f => f.Size >= size);
            if (idx < 0)
                idx = this.FileLists.FindLastIndex(f => f.Size < size);

            if (shift != 0)
            {
                idx += shift;
                if (idx < 0) idx = 0;
                int last = this.FileLists.Count - 1;
                if (idx > last) idx = last;
            }

            return this.FileLists[idx];
        }
        /// <summary>
        /// Return an list of all FileList sorted for specified "size":
        /// at index [0] is FileList with required Size (or greater), at next positions is FileList with Size greater than "size" (ASC), 
        /// and then with Size smaller than "size" (DESC).
        /// </summary>
        /// <param name="size"></param>
        /// <returns></returns>
        protected List<FileList> GetFileListsForSize(int size)
        {
            List<FileList> result = new List<FileList>(this.FileLists);
            result.Sort((a, b) => FileListCompareForSize(a, b, size));
            return result;
        }
        /// <summary>
        /// Comparer for method GetFileListsForSize()
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <param name="size"></param>
        /// <returns></returns>
        private int FileListCompareForSize(FileList a, FileList b, int size)
        {
            int ac = a.Size.CompareTo(size);     // ac is positive when a.Size > size, is zero when a.Size == size, is negative when a.Size < size
            int bc = b.Size.CompareTo(size);     // bc is positive when b.Size > size, is zero when b.Size == size, is negative when b.Size < size
            
            if (ac == 0 && bc == 0) return 0;    // a.Size == size, b.Size == size => a and be is equal
            if (ac == 0) return -1;              // a.Size == size, b.Size != size => a is better (-1 : a will be at index near [0] after Sort)
            if (bc == 0) return 1;               // a.Size != size, b.Size == size => b is better (+1 : b will be at index near [0] after Sort)

            if (ac > 0 && bc < 0) return -1;     // a.Size > size, b.Size < size => a is better
            if (ac < 0 && bc > 0) return 1;      // a.Size < size, b.Size > size => b is better

            int ab = a.Size.CompareTo(b.Size);   // ab is negative when a.Size < b.Size
            if (ac > 0 && ab < 0) return -1;     // a.Size > size (and b.Size > size) and a.Size < b.Size => a is better
            if (ac > 0 && ab > 0) return 1;      // a.Size > size (and b.Size > size) and a.Size > b.Size => b is better
            if (ac < 0 && ab < 0) return 1;      // a.Size < size (and b.Size < size) and a.Size < b.Size => b is better
            if (ac < 0 && ab > 0) return -1;     // a.Size < size (and b.Size < size) and a.Size > b.Size => a is better

            return 0;
        }
        /// <summary>
        /// Return array of state for specified state, where at first position [0] is specified state, 
        /// and on next positions is supplemental states for icons, when does not exists icon for exact state.
        /// </summary>
        /// <param name="state"></param>
        /// <returns></returns>
        protected IconState[] GetStateWithSupplement(IconState state)
        {
            return this._StateSupplements[state];
        }
        /// <summary>
        /// Search for file with icon and load it
        /// </summary>
        /// <param name="fileKey">Name of file, without a size and state suffix</param>
        /// <param name="fileList">List of files with requested size</param>
        /// <param name="state">State of icon</param>
        /// <returns></returns>
        protected System.Drawing.Image LoadImage(string fileKey, FileList fileList, IconState state)
        {
            string fileName = this._SearchForFileName(fileKey, this._StateSuffix[state], this._FileExtensions, fileList);
            if (fileName == null) return null;

            try
            {
                return System.Drawing.Bitmap.FromFile(fileName);
            }
            catch { }
            return null;
        }

        private string _SearchForFileName(string fileKey, string[] suffixes, string[] extensions, FileList fileList)
        {
            foreach (string ext in extensions)
                foreach (string suf in suffixes)
                {
                    string key = fileKey + suf + ext;
                    string fileName;
                    if (fileList.Files.TryGetValue(key, out fileName)) return fileName;
                }
            return null;
        }

       
        /// <summary>
        /// Full directory name for path with images: DLL files / pic
        /// </summary>
        private string _ImagePath;
        /// <summary>
        /// Dictionary of images: 
        /// Key = name of image without size and state,
        /// Value = IconImages (contain all size and all state, can on demand load new image, return requested image).
        /// Contain only object on demand loaded for requested names, not for all existing files.
        /// </summary>
        private Dictionary<string, IconImages> _ImageDict;
        /// <summary>
        /// List of all directories and its file names
        /// </summary>
        protected List<FileList> FileLists { get { return this._FileLists; } }
        /// <summary>
        /// List of all directories and its file names
        /// </summary>
        private List<FileList> _FileLists;
        /// <summary>
        /// List of file suffixes for each icon state
        /// </summary>
        private Dictionary<IconState, string[]> _StateSuffix;
        /// <summary>
        /// List of state supplements for specified state (when does not exists icon for specified state, we can return icon for other state)
        /// </summary>
        private Dictionary<IconState, IconState[]> _StateSupplements;
        /// <summary>
        /// List of file extensions, by their priority
        /// </summary>
        private string[] _FileExtensions;
        #endregion
        #region class FileList, IconImages
        /// <summary>
        /// One pic directory with its size and content (file list)
        /// </summary>
        protected class FileList
        {
            private FileList(System.IO.DirectoryInfo dirInfo, int size)
            {
                this.Path = dirInfo.FullName;
                this.Size = size;
                this._Files = null;
            }
            /// <summary>
            /// Visualisation
            /// </summary>
            /// <returns></returns>
            public override string ToString()
            {
                return "Size=" + this.Size.ToString() + "; Path=" + this.Path;
            }
            /// <summary>
            /// Full directory name for images of this.Size
            /// </summary>
            public string Path { get; private set; }
            /// <summary>
            /// Size of all images in this directory in typicall line: 16,24,32,48,64
            /// </summary>
            public int Size { get; private set; }
            /// <summary>
            /// List of names of all existing files in this directory
            /// </summary>
            public Dictionary<string, string> Files
            {
                get
                {
                    if (this._Files == null)
                    {
                        List<string> files = new List<string>(System.IO.Directory.GetFiles(this.Path));
                        files.Sort();
                        Dictionary<string, string> dict = new Dictionary<string, string>();
                        foreach (string file in files)
                        {
                            string key = System.IO.Path.GetFileName(file).ToLower();
                            dict.Add(key, file);
                        }
                        this._Files = dict;
                    }
                    return this._Files;
                }
            }
            private Dictionary<string, string> _Files;
            /// <summary>
            /// Return new FileList instance for specified directory.
            /// Return null, when directory does not exists or its name not ending with numeric (with length 1÷3 digits).
            /// </summary>
            /// <param name="subDir"></param>
            /// <returns></returns>
            internal static FileList CreateForDir(System.IO.DirectoryInfo subDir)
            {
                if (subDir == null || !subDir.Exists) return null;
                string name = subDir.Name;            // for example: "pic16"
                string text = "";
                for (int i = name.Length - 1; i >= 0; i--)
                {
                    char c = name[i];
                    if ("0123456789".IndexOf(c) < 0) break;
                    text = c.ToString() + text;
                }
                if (text.Length == 0 || text.Length > 3) return null;

                int size;
                if (!Int32.TryParse(text, out size)) return null;
                return new FileList(subDir, size);
            }
        }
        /// <summary>
        /// One icon with all its images (all sizes, all states)
        /// </summary>
        protected class IconImages
        {
            public IconImages(string key, string name, IconLib owner)
            {
                this.Key = key;
                this.Name = name;
                this.Owner = owner;
                this.ImageList = new List<IconImageSize>();
            }
            /// <summary>
            /// Visualisation
            /// </summary>
            /// <returns></returns>
            public override string ToString()
            {
                return "Image key=" + this.Key + "; loaded " + this.ImageList.Count.ToString() + " size(s).";
            }
            /// <summary>
            /// Key for image = lower, trimmed name of icon
            /// </summary>
            public string Key { get; private set; }
            /// <summary>
            /// Name of icon
            /// </summary>
            public string Name { get; private set; }
            /// <summary>
            /// Reference to owner object
            /// </summary>
            protected IconLib Owner { get; private set; }
            /// <summary>
            /// Reference to list of directories with icon files
            /// </summary>
            public List<FileList> FileLists { get { return this.Owner._FileLists; } }
            /// <summary>
            /// List of this icon in all sizes
            /// </summary>
            public List<IconImageSize> ImageList { get; private set; }
            /// <summary>
            /// Get image
            /// </summary>
            /// <param name="size"></param>
            /// <param name="state"></param>
            /// <returns></returns>
            internal Image Image(int size, IconState state)
            {
                List<FileList> fileLists = this.Owner.GetFileListsForSize(size);
                IconState[] states = this.Owner.GetStateWithSupplement(state);

                foreach (IconState s in states)
                    foreach (FileList fl in fileLists)
                    {
                        Image i = this._GetExactImage(fl, s);
                        if (i != null) return i;
                    }

                return null;
            }
            /// <summary>
            /// Return specified image, can try load image from file on first request.
            /// When image does not exists, return null (dont return any other = supplemental image).
            /// </summary>
            /// <param name="fileList"></param>
            /// <param name="state"></param>
            /// <returns></returns>
            private Image _GetExactImage(FileList fileList, IconState state)
            {
                IconImageSize imageSize = this.GetImageForSize(fileList.Size);
                if (imageSize == null) return null;

                // Quick resolve:
                if (imageSize.HasImage(state)) return imageSize[state];

                // OnDemand load:
                if (!imageSize.IsLoaded(state))
                    imageSize[state] = this.Owner.LoadImage(this.Key, fileList, state);

                // Is now loaded?
                if (imageSize.HasImage(state)) return imageSize[state];

                return null;
            }
            /// <summary>
            /// Returns set of images for specified size (or nearest upper, or nearest lower).
            /// If "shift" is not zero, then return images smaller (for negative shift) or bigger (positive shift).
            /// Value of "shift" is not in pixel, but in count of "set".
            /// For example, when exists size: 8,16,24,32,48,64, and requested size = 32, and shift= -1, then return is with size = 24.
            /// For requested size = 28 and shift = 0 is return set size = 32 (nearest bigger), for shift= -1, then return is with size = 24.
            /// Returns null only when does not exists any path (this.FileLists.Count == 0).
            /// </summary>
            /// <param name="size"></param>
            /// <param name="shift"></param>
            /// <returns></returns>
            private IconImageSize GetImageForSize(int size)
            {
                IconImageSize imageSize = this.ImageList.FirstOrDefault(i => i.Size == size);
                if (imageSize == null)
                {
                    imageSize = new IconImageSize(size);
                    this.ImageList.Add(imageSize);
                    if (this.ImageList.Count > 1)
                        this.ImageList.Sort((a, b) => (a.Size.CompareTo(b.Size)));
                }
                return imageSize;
            }
        }
        /// <summary>
        /// One icon in one size, with all states
        /// </summary>
        protected class IconImageSize
        {
            public IconImageSize(int size)
            {
                this.Size = size;
                this.ImageDict = new Dictionary<IconState, Image>();
            }
            /// <summary>
            /// Visualisation
            /// </summary>
            /// <returns></returns>
            public override string ToString()
            {
                return "Images for size=" + this.Size.ToString() + "; loaded images count=" + this.ImageDict.Count.ToString();
            }
            /// <summary>
            /// Return true when contains item for specified state.
            /// When item for state is a null value, return true (contains null).
            /// </summary>
            /// <param name="state"></param>
            /// <returns></returns>
            public bool IsLoaded(IconState state)
            {
                return this.ImageDict.ContainsKey(state);
            }
            /// <summary>
            /// Return true when contains non-null image for specified state.
            /// When item for state is a null value, return false (does not contains an image).
            /// </summary>
            /// <param name="state"></param>
            /// <returns></returns>
            public bool HasImage(IconState state)
            {
                return (this.ImageDict.ContainsKey(state) && this.ImageDict[state] != null);
            }
            /// <summary>
            /// Get or set an Image for specified state.
            /// When image for state does not exists (or exists and contain null), return null.
            /// </summary>
            /// <param name="state"></param>
            /// <returns></returns>
            public Image this[IconState state]
            {
                get 
                {
                    Image image;
                    if (this.ImageDict.TryGetValue(state, out image))
                        return image;
                    return null;
                }
                set
                {
                    if (!this.ImageDict.ContainsKey(state))
                        this.ImageDict.Add(state, value);
                    else
                        this.ImageDict[state] = value;
                }
            }
            public int Size { get; private set; }
            public Dictionary<IconState, Image> ImageDict { get; private set; }
        }
        #endregion
    }
    #region enum IconSize and IconState
    /// <summary>
    /// Enum IconSize (8; 16; 24; 32; 48; 64)
    /// </summary>
    public enum IconSize : int
    {
        Micro8 = 8,
        Mini16 = 16,
        Small24 = 24,
        Standard32 = 32,
        Enlarged48 = 48,
        Big64 = 64
    }
    public enum IconState : int
    {
        Standard = 'a',
        Disable = 'd',
        Hot = 'h',
        Focused = 'f',
        Pressed = 'p'
    }
    #endregion
}
